/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016 NXP
 * All rights reserved.
 *
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
#include "erpc_client_tcp_transport.h"
#include <cstdio>
#include <errno.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#ifdef WINSOCK_USED
#include <WS2tcpip.h>
#define _CLOSE_SOCKET ::closesocket
#define _SD_BOTH_ SD_BOTH
#define _IS_INVALID_SOCKET_(s) (s == INVALID_SOCKET)
#pragma comment(lib,"Ws2_32.lib ")
#elif defined(__unix)
#define INVALID_SOCKET -1
#define _CLOSE_SOCKET ::close
#define _SD_BOTH_ SHUT_RDWR
#define _IS_INVALID_SOCKET_(s) (s < 0)
#include <err.h>
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#endif
#include "raii.h"
using namespace erpc;
using namespace gdface;
// Set this to 1 to enable debug logging.
// TODO fix issue with the transport not working on Linux if debug logging is disabled.
//#define TCP_TRANSPORT_DEBUG_LOG (1)

#if TCP_TRANSPORT_DEBUG_LOG
#define TCP_DEBUG_PRINT(_fmt_, ...) printf(_fmt_, ##__VA_ARGS__)
#define TCP_DEBUG_ERR(_msg_) err(errno, _msg_)
#else
#define TCP_DEBUG_PRINT(_fmt_, ...)
#define TCP_DEBUG_ERR(_msg_)
#endif

thread_local _SOCK_TYPE_ ClientTCPTransport::m_socket = -1;

////////////////////////////////////////////////////////////////////////////////
// Code
////////////////////////////////////////////////////////////////////////////////

ClientTCPTransport::ClientTCPTransport()
: m_host(NULL)
, m_port(0)
{
}

ClientTCPTransport::ClientTCPTransport(const char *host, uint16_t port)
: m_host(host)
, m_port(port)
{
}

ClientTCPTransport::~ClientTCPTransport(void) {}

void ClientTCPTransport::configure(const char *host, uint16_t port)
{
    m_host = host;
    m_port = port;
}

erpc_status_t ClientTCPTransport::open(void)
{
    if (!_IS_INVALID_SOCKET_(m_socket))
    {
        TCP_DEBUG_PRINT("socket already connected %d\n",m_socket);
        return kErpcStatus_Success;
    }
#ifdef WINSOCK_USED
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		TCP_DEBUG_ERR("failed to WSAStartup");
		return kErpcStatus_InitFailed;
	}
	if (LOBYTE(wsaData.wVersion) != 2 ||
		HIBYTE(wsaData.wVersion) != 2) {
		WSACleanup();
		TCP_DEBUG_ERR("Winsocket version mismatch");
		return kErpcStatus_InitFailed;
	}
#endif
    // Fill in hints structure for getaddrinfo.
    struct addrinfo hints = { 0 };
    hints.ai_flags = AI_NUMERICSERV;
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    // Convert port number to a string.
    char portString[8];
    snprintf(portString, sizeof(portString), "%d", m_port);

    // Perform the name lookup.
    struct addrinfo *res0;
    int result = getaddrinfo(m_host, portString, &hints, &res0);
    if (result)
    {
        // TODO check EAI_NONAME
        TCP_DEBUG_ERR("gettaddrinfo failed");
        return kErpcStatus_UnknownName;
    }

    // Iterate over result addresses and try to connect. Exit the loop on the first successful
    // connection.
	_SOCK_TYPE_ sock = INVALID_SOCKET;
    struct addrinfo *res;
    for (res = res0; res; res = res->ai_next)
    {
        // Create the socket.
        sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        if (_IS_INVALID_SOCKET_(sock))
        {
            continue;
        }

        // Attempt to connect.
        if (connect(sock, res->ai_addr, res->ai_addrlen) < 0)
        {
			_CLOSE_SOCKET(sock);
            sock = INVALID_SOCKET;
            continue;
        }

        // Exit the loop for the first successful connection.
        break;
    }

    // Free the result list.
    freeaddrinfo(res0);

    // Check if we were able to open a connection.
    if (sock < 0)
    {
        // TODO check EADDRNOTAVAIL:
        TCP_DEBUG_ERR("connecting failed");
        return kErpcStatus_ConnectionFailure;
    }

// On some systems (BSD) we can disable SIGPIPE on the socket. For others (Linux), we have to
// ignore SIGPIPE.
#ifdef WINSOCK_USED
	// DO NOTHING
#elif defined(SO_NOSIGPIPE)
    // Disable SIGPIPE for this socket. This will cause write() to return an EPIPE error if the
    // other side has disappeared instead of our process receiving a SIGPIPE.
    int set = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)) < 0)
    {
		_CLOSE_SOCKET(sock);
        TCP_DEBUG_ERR("setsockopt failed");
        return kErpcStatus_Fail;
    }
#else
    // globally disable the SIGPIPE signal
    signal(SIGPIPE, SIG_IGN);
#endif // defined(SO_NOSIGPIPE)
    m_socket = sock;

    return kErpcStatus_Success;
}

erpc_status_t ClientTCPTransport::close(void)
{
    if (!_IS_INVALID_SOCKET_(m_socket))
    {
		TCP_DEBUG_PRINT("client socket %d closed\n",m_socket);
		_CLOSE_SOCKET(m_socket);
        m_socket = INVALID_SOCKET;
    }

    return kErpcStatus_Success;
}

static thread_local int underlyingReceive_count = 0;
erpc_status_t ClientTCPTransport::underlyingReceive(uint8_t *data, uint32_t size)
{
	gdface::raii raii_socket(
			[&]() {
		// 当前方法被调用２次后client端接收结束关闭client socket
		if(!_IS_INVALID_SOCKET_(m_socket)){
			if( (underlyingReceive_count > 0)){
				close();
				underlyingReceive_count = 0;
			}else{
				underlyingReceive_count ++;
			}
		}
	});
	if( m_socket <= 0){
		underlyingReceive_count = 1;
		return kErpcStatus_ConnectionFailure;
	}

#ifdef WINSOCK_USED
	decltype(recv(0, nullptr, 0, 0)) length = 0;
#else
	decltype(read(0, nullptr, 0)) length = 0;
#endif
    // Loop until all requested data is received.
    while (size)
    {
#ifdef WINSOCK_USED
		length = recv(m_socket, (char*)data, (int)size, 0);
#else
		length = read(m_socket, data, size);
#endif
        if (length < 0)
        {
        	underlyingReceive_count = 1;
            return kErpcStatus_ReceiveFailed;
        }
        else
        {
            size -= length;
            data += length;
        }
    }

    return kErpcStatus_Success;
}

erpc_status_t ClientTCPTransport::underlyingSend(const uint8_t *data, uint32_t size)
{
    if (m_socket <= 0)
    {
    	// 发送时创建socket
    	auto status = open();
    	if(kErpcStatus_Success != status)
    	{
    		return status;
    	}
    	TCP_DEBUG_PRINT("client socket %d open\n",m_socket);
    }

    // Loop until all data is sent.
    while (size)
    {
#ifdef WINSOCK_USED
		auto result = recv(m_socket, (char*)data, (int)size, 0);
#else
		auto result = write(m_socket, data, size);
#endif  
        if (result >= 0)
        {
            size -= result;
            data += result;
        }
        else
        {
            if (errno == EPIPE)
            {
                // Server closed.
                close();
                return kErpcStatus_ConnectionClosed;
            }
            return kErpcStatus_SendFailed;
        }
    }

    return kErpcStatus_Success;
}


